unit MemoryLoadLibraryUnit;

interface
uses
  Winapi.Windows,System.SysUtils;

/// <summary>
/// 内存加载DLL
/// </summary>
/// <param name="FileMemory">DLL文件内存映射地址</param>
/// <param name="FileSize">DLL文件大小</param>
/// <returns>DLL句柄</returns>
function LoadLibraryX(const FileMemory:Pointer;const FileSize:ULONG):THandle;
/// <summary>
/// 获取dll导出函数
/// </summary>
/// <param name="MemoryLibrary">DLL句柄</param>
/// <param name="API">API名称</param>
/// <returns>函数指针</returns>
function GetMemoryLibraryProcAddress(Const MemoryLibrary:THandle;const API:PAnsiChar):Pointer;
/// <summary>
/// 调用dllmain
/// </summary>
/// <param name="MemoryLibrary">DLL句柄</param>
/// <param name="dwReason">事件量，DLL_PROCESS_ATTACH，DLL_THREAD_ATTACH，DLL_THREAD_DETACH，DLL_PROCESS_DETACH</param>
/// <remarks>
///  尽量应当在相应时机调用相应事件，LoadlibraryX已调用DLL_PROCESS_ATTACH事件。
/// </remarks>
procedure CallDllMain(Const MemoryLibrary:THandle;dwReason:DWORD);
implementation

//兼容32&64的内存加载模块  作者：妖蛋
//参考：Anskya的MemLibrary、看雪论坛、CSDN、武稀松Blog
//特别崇拜一下武大神的MemoryModule，写的好流弊。
function LoadLibraryX(const FileMemory:Pointer;const FileSize:ULONG):THandle;
type
    _IMAGE_BASE_RELOCATION = packed record
      VirtualAddress:dword;  //重定位数据开始RVA
      SizeOfBlock:dword;     //重定位长度     (SizeOfBlock - $8)/2
    end;
    PIMAGE_BASE_RELOCATION = ^_IMAGE_BASE_RELOCATION;
    PImageBaseRelocation = PIMAGE_BASE_RELOCATION;

    PRUNTIME_FUNCTION = ^TRUNTIME_FUNCTION;
    TRUNTIME_FUNCTION = record
      FunctionStart: LongWord;
      FunctionEnd: LongWord;
      UnwindInfo: LongWord;
    end;

var
  NtHeaders:PImageNtHeaders;
  ImageBase:NativeUInt;
  LoadMem:Pointer;
  IsImageBaseLoad:Boolean;
  SectionArray:array of TImageSectionHeader;
  protectvalue, oldProtect: DWORD;
  pgsize: NativeUInt;
  protect:DWORD;
  i:DWORD;
  ImportHeader:PImageImportDescriptor;
  ImportDllHandle:THandle;
  ThunkP,WriteThunkP:PImageThunkData;
  BaseRelocation:PImageBaseRelocation;
  BaseItem:Pointer;
  BaseNumber:NativeUInt;
  P:Pointer;
  PruntIME: PRUNTIME_FUNCTION;
  PruntIMENumber:DWORD;
  DllMain: function (dwHandle:THandle;dwReason:DWORD;dwReserved: Pointer): Boolean; stdcall;
  RtlAddFunctionTable:function (FunctionTable:PRUNTIME_FUNCTION;EntryCount:DWORD;BaseAddress:DWORD64):Boolean;cdecl;
label
  OVER;
begin
  Result := 0;
  //获取PE头信息
  NtHeaders := PImageNtHeaders(Pointer(NativeUInt(FileMemory) + PImageDosHeader(FileMemory)^._lfanew));
  //获取加载地址
  ImageBase := NtHeaders.OptionalHeader.ImageBase;
  //尝试加载
  LoadMem := AllocMem(NtHeaders.OptionalHeader.SizeOfImage);
  //将页面调整为可读可写可执行，所有都完成后再修正内存页属性
  VirtualProtect(LoadMem,NtHeaders.OptionalHeader.SizeOfImage,PAGE_EXECUTE_READWRITE,&protect);
  //判断是否为默认加载位置
  if NativeUInt(LoadMem) = ImageBase then  IsImageBaseLoad := True
  else IsImageBaseLoad := False;
  //复制PE头到加载地址
  Move(FileMemory^,LoadMem^,NtHeaders.OptionalHeader.SizeOfHeaders);
  //申请区块数组
  SetLength(SectionArray,NtHeaders.FileHeader.NumberOfSections);
  //得到节表
  {$IFDEF  Win64}
  Move(Pointer(NativeUInt(FileMemory) + SizeOf(TImageNtHeaders64) + PImageDosHeader(FileMemory)^._lfanew)^,
      SectionArray[0],
      SizeOf(TImageSectionHeader) * NtHeaders.FileHeader.NumberOfSections);
  {$ELSE}
  Move(Pointer(NativeUInt(FileMemory) + SizeOf(TImageNtHeaders32) + PImageDosHeader(FileMemory)^._lfanew)^,
      SectionArray[0],
      SizeOf(TImageSectionHeader) * NtHeaders.FileHeader.NumberOfSections);
  {$ENDIF}
   if NtHeaders.OptionalHeader.AddressOfEntryPoint <> 0 then
      @DllMain := Pointer(NtHeaders.OptionalHeader.AddressOfEntryPoint + NativeUInt(LoadMem));
  //循环加载区块
  for I := 0 to NtHeaders.FileHeader.NumberOfSections - 1 do
  begin
    //未初始化数据区段的计算方式，参考自武稀松的MemoryModule
    pgsize := SectionArray[I].SizeOfRawData;
    if pgsize = 0 then
    begin
      if (SectionArray[I].Characteristics and IMAGE_SCN_CNT_INITIALIZED_DATA) = IMAGE_SCN_CNT_INITIALIZED_DATA
      then
      begin
        pgsize := NtHeaders.OptionalHeader.SizeOfInitializedData;
      end
      else if (SectionArray[I].Characteristics and IMAGE_SCN_CNT_UNINITIALIZED_DATA) = IMAGE_SCN_CNT_UNINITIALIZED_DATA
      then
      begin
        pgsize := NtHeaders.OptionalHeader.SizeOfUninitializedData;
      end
    end;
    //注意未初始化的数据的SizeOfRawData为0，需要计算。
    Move(Pointer(NativeUInt(FileMemory) + SectionArray[I].PointerToRawData)^,Pointer(NativeUInt(LoadMem) + SectionArray[I].VirtualAddress)^,pgsize);
  end;
  //判断是否有导入表
  //提示：这里其实可以不必判断，因为NT系统要求至少有一个导入表项。
  if (NtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress <> 0) and
     (NtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size <> 0) then
  begin
    //获取导入表指针
    ImportHeader := Pointer(NtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress + NativeUInt(LoadMem));
    //循环遍历指针
    while (ImportHeader.FirstThunk <> 0) and (ImportHeader.Name <> 0) do
    begin
      //加载DLL
      ImportDllHandle := LoadLibraryA(PAnsiChar(Pointer(ImportHeader.Name + NativeUInt(LoadMem))));
      //加载失败则失败
      if ImportDllHandle = 0 then goto OVER;
      //这里会有加载失败的情况，暂时不做处理，自己必须进行处理。


        //获取FirstThunk数组的指针
        ThunkP := Pointer(ImportHeader.FirstThunk + NativeUInt(LoadMem));
        WriteThunkP := Pointer(ImportHeader.FirstThunk + NativeUInt(LoadMem));
        //TimeDateStamp为-1时，使用Characteristics作为绑定地址   参考自：MemLibrary
        if ImportHeader.TimeDateStamp = -1 then ThunkP := Pointer(ImportHeader.Characteristics + NativeUInt(LoadMem))
        else ThunkP := WriteThunkP;

        while ThunkP._Function <> 0 do
        begin
            {$IFDEF  Win64}
            if (ThunkP._Function and $8000000000000000) <> 0 then
            {$ELSE}
            if (ThunkP._Function and $80000000) <> 0 then
            {$ENDIF}
            begin
              WriteThunkP._Function :=  NativeUInt(GetMemoryLibraryProcAddress(ImportDllHandle, PAnsiChar(Cardinal(ThunkP._Function) and $FFFF)));
            end
            else
            begin
              //这个name前面有2个字节的头，要加2才能得到正确的字符串
              WriteThunkP._Function := NativeUInt(GetMemoryLibraryProcAddress(ImportDllHandle, PAnsiChar(Pointer(NativeUInt(LoadMem) + ThunkP.ForwarderString + 2))));
            end;
           ThunkP:=  Pointer(NativeUInt(Pointer(ThunkP)) + SizeOf(TImageThunkData));
           WriteThunkP := Pointer(NativeUInt(Pointer(WriteThunkP)) + SizeOf(TImageThunkData));
        end;


      ImportHeader := Pointer(NativeUInt(ImportHeader) + SizeOf(_IMAGE_IMPORT_DESCRIPTOR));
    end;
  end;
  //重定位表
  //如果存在重定位表且需要重定位则进行重定位
  //注意：由于NT中PE的重定位表并不是必须存在，为兼容加壳后的没有重定位表存在的PE结构，这里不强制要求进行重定位。
  if (NtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress <> 0) and
     (NtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size <> 0) and not IsImageBaseLoad then
  begin

     //获取重定位结构指针
     BaseRelocation := Pointer(NtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress + NativeUInt(LoadMem));
     while BaseRelocation.VirtualAddress <> 0 do
     begin

       //计算重定位数组数量
       BaseNumber := (BaseRelocation.SizeOfBlock - SizeOf(_IMAGE_BASE_RELOCATION)) div $2;
       BaseItem := Pointer(NativeUInt(BaseRelocation) + SizeOf(_IMAGE_BASE_RELOCATION));

       for i := 0 to BaseNumber - 1 do
       begin
         {$IFDEF Win64}
         if ((WORD(BaseItem^) and $A000) = $A000) then
         begin
           P := Pointer((WORD(BaseItem^) mod $A000 + BaseRelocation.VirtualAddress) +  NativeUInt(LoadMem));
           //如果重定位目标指针无效则忽略
           if Assigned(P) then
            NativeUInt(P^) := NativeUInt(p^) - ImageBase + NativeUInt(LoadMem);
         end;
        {$ELSE}
         if ((WORD(BaseItem^) and $3000) = $3000) then
         begin
           P := Pointer((WORD(BaseItem^) mod $3000 + BaseRelocation.VirtualAddress) +  NativeUInt(LoadMem));
           //如果重定位目标指针无效则忽略
           if Assigned(P) then
            NativeUInt(P^) := NativeUInt(p^) - ImageBase + NativeUInt(LoadMem);
         end;
        {$ENDIF}
        NativeUInt(BaseItem) :=  NativeUInt(BaseItem) + SizeOf(WORD);
       end;

       NativeUInt(BaseRelocation) :=  NativeUInt(BaseRelocation) + BaseRelocation.SizeOfBlock;
     end;
  end;

  //异常表
  //修改加载地址
  PImageNtHeaders(Pointer(NativeUInt(LoadMem) + PImageDosHeader(LoadMem)^._lfanew)).OptionalHeader.ImageBase := NativeUInt(LoadMem);
  {$IFDEF  Win64}
  //64位需要注册异常结构  参考：Engine、安全客、msdn、MahdiSafsafi/DebugEngine
  @RtlAddFunctionTable := GetMemoryLibraryProcAddress(GetModuleHandle('Kernel32.dll'),'RtlAddFunctionTable');
  //如果api获取成功则开始注册
  if Assigned(RtlAddFunctionTable) then
  begin
    //判断是否有异常表
    if PImageNtHeaders(Pointer(NativeUInt(LoadMem) +
      PImageDosHeader(LoadMem)^._lfanew)).OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].
      VirtualAddress <> 0 then
    begin
       PruntIME := Pointer(PImageNtHeaders(Pointer(NativeUInt(LoadMem) +
          PImageDosHeader(LoadMem)^._lfanew)).OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].
          VirtualAddress + NativeUInt(LoadMem));

       PruntIMENumber :=PImageNtHeaders(Pointer(NativeUInt(LoadMem) + PImageDosHeader(LoadMem)^._lfanew)).OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size div sizeof(TRUNTIME_FUNCTION);
       RtlAddFunctionTable(PruntIME,PruntIMENumber,dword64(LoadMem));
    end;
  end;
  {$ENDIF}
  //去掉所有页执行权限
  VirtualProtect(LoadMem,NtHeaders.OptionalHeader.SizeOfHeaders,PAGE_READWRITE,&protect);
  //按照权限进行设置，参考自武稀松的MemoryModule
  for I := 0 to NtHeaders.FileHeader.NumberOfSections - 1 do
  begin
    protectvalue := 0;

    //设置权限，武大的算法写错了！
    if ((SectionArray[I].Characteristics and IMAGE_SCN_MEM_EXECUTE) = IMAGE_SCN_MEM_EXECUTE)
    and ((SectionArray[I].Characteristics and IMAGE_SCN_MEM_WRITE) = IMAGE_SCN_MEM_WRITE)
    and ((SectionArray[I].Characteristics and IMAGE_SCN_MEM_READ) = IMAGE_SCN_MEM_READ) then
       protectvalue := PAGE_EXECUTE_READWRITE
    else
    if ((SectionArray[I].Characteristics and IMAGE_SCN_MEM_EXECUTE) = IMAGE_SCN_MEM_EXECUTE)
    and ((SectionArray[I].Characteristics and IMAGE_SCN_MEM_READ) = IMAGE_SCN_MEM_READ) then
       protectvalue := PAGE_EXECUTE_READ
    else
    if ((SectionArray[I].Characteristics and IMAGE_SCN_MEM_EXECUTE) = IMAGE_SCN_MEM_EXECUTE)
    and ((SectionArray[I].Characteristics and IMAGE_SCN_MEM_WRITE) = IMAGE_SCN_MEM_WRITE)
    and ((SectionArray[I].Characteristics and IMAGE_SCN_MEM_READ) = IMAGE_SCN_MEM_READ) then
       protectvalue := PAGE_EXECUTE_WRITECOPY
    else
    begin
      if ((SectionArray[I].Characteristics and IMAGE_SCN_MEM_READ) = IMAGE_SCN_MEM_READ)
         and ((SectionArray[I].Characteristics and IMAGE_SCN_MEM_WRITE) = IMAGE_SCN_MEM_WRITE) then
         protectvalue := PAGE_READWRITE
      else
      if ((SectionArray[I].Characteristics and IMAGE_SCN_MEM_READ) = IMAGE_SCN_MEM_READ) then
         protectvalue := PAGE_READONLY
      else
      if ((SectionArray[I].Characteristics and IMAGE_SCN_MEM_WRITE) = IMAGE_SCN_MEM_WRITE) then
         protectvalue := PAGE_WRITECOPY;
    end;

    pgsize := SectionArray[I].SizeOfRawData;
    if pgsize = 0 then
    begin
      if (SectionArray[I].Characteristics and IMAGE_SCN_CNT_INITIALIZED_DATA) = IMAGE_SCN_CNT_INITIALIZED_DATA
      then
      begin
        pgsize := NtHeaders.OptionalHeader.SizeOfInitializedData;
      end
      else if (SectionArray[I].Characteristics and IMAGE_SCN_CNT_UNINITIALIZED_DATA) = IMAGE_SCN_CNT_UNINITIALIZED_DATA
      then
      begin
        pgsize := NtHeaders.OptionalHeader.SizeOfUninitializedData;
      end
    end;

    //对齐
    if pgsize mod NtHeaders.OptionalHeader.SectionAlignment > 0 then
       pgsize := ((pgsize div NtHeaders.OptionalHeader.SectionAlignment) + 1) * NtHeaders.OptionalHeader.SectionAlignment;

    if pgsize > 0 then
    begin
      OutputDebugString(PWideChar(Format('设置地址:%Xd   设置大小：%Xd    结束地址：%Xd',
      [SectionArray[I].VirtualAddress + NativeUInt(LoadMem),
      pgsize,
      SectionArray[I].VirtualAddress + NativeUInt(LoadMem) + pgsize])));
      if VirtualProtect(Pointer(SectionArray[I].VirtualAddress + NativeUInt(LoadMem)), pgsize, protectvalue, oldProtect)
      then
        OutputDebugString('设置成功！');
    end;
  end;

  //全部完成后，返回加载指针为句柄
  //存在的问题：带压缩的VMP加载失败，不压缩可以加载
  if NtHeaders.OptionalHeader.AddressOfEntryPoint <> 0 then
    DllMain(Result, DLL_PROCESS_ATTACH,nil);

  //返回句柄
  Result := THandle(LoadMem);
  OVER:
  SetLength(SectionArray,0);
end;


function GetMemoryLibraryProcAddress(Const MemoryLibrary:THandle;const API:PAnsiChar):Pointer;
var
  Memory:Pointer;
  NTHeader:PImageNtHeaders;
  ExportHeader:PImageExportDirectory;
  ExporFunPointer:PDWORD;
  ExporNamePointer:PDWORD;
  NameOrdinalsPointer:PWORD;
  FindendOrdinal:Integer;
  ExporFunStr:PAnsiChar;
  //为了避免申请内存，也是拼了
  ExporFunDllName:array [0..255] of AnsiChar;
  ExporFunAPIName:array [0..255] of AnsiChar;
  TmpBoolean :Boolean;
  i,dlllength: Integer;
begin

   Result := nil;
   Memory := Pointer(MemoryLibrary);
   NTHeader := PImageNtHeaders(Pointer(PImageDosHeader(Memory)._lfanew + NativeUInt(Memory)));
   if (NTHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress <> 0) and
      (NTHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size <> 0) then
   begin
       ExportHeader := PImageExportDirectory(Pointer(NTHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress + NativeUInt(Memory)));
       ExporFunPointer := Pointer(ExportHeader.AddressOfFunctions + NativeUInt(Memory));
       ExporNamePointer := Pointer(ExportHeader.AddressOfNames + NativeUInt(Memory));
       NameOrdinalsPointer := Pointer(ExportHeader.AddressOfNameOrdinals + NativeUInt(Memory));
       FindendOrdinal := -1;

       if (NativeUInt(API) > $FFFF) then
       begin

         for i := 0 to exportheader.NumberOfNames - 1 do
         begin
         // OutputDebugStringA(Pointer(ExporNamePointer^ + NativeUInt(Memory)));
           if StrIComp(PAnsiChar(ExporNamePointer^ + NativeUInt(Memory)),API) = 0 then
           begin
              FindendOrdinal := NameOrdinalsPointer^;
              Break;
           end;

           NativeUInt(ExporNamePointer) :=  NativeUInt(ExporNamePointer) + SizeOf(dword);
           NativeUInt(NameOrdinalsPointer) :=  NativeUInt(NameOrdinalsPointer) + SizeOf(Word);
         end;

       end
       else
        FindendOrdinal := DWord(API) - ExportHeader.Base;    {减法！！不是加法}

       if FindendOrdinal = -1 then Exit;

       NativeUInt(ExporFunPointer) :=  NativeUInt(ExporFunPointer) + (SizeOf(DWORD) * FindendOrdinal);
      //两种类型，需要处理导出表link
       if  (NTHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress > NativeUInt(ExporFunPointer^))
        or (NTHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress + NTHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size < NativeUInt(ExporFunPointer^))then
                   Result := Pointer(NativeUInt(ExporFunPointer^) + NativeUInt(Memory))
       else
       begin
          Result := GetProcAddress(MemoryLibrary,API);
          if Result = nil then
          begin
            //处理导出表指针问题
            //强撸字符串
            ExporFunStr := PAnsiChar(NativeUInt(ExporFunPointer^) + NativeUInt(Memory));
            //格式:DLLNAME.API
            i:= 0;
            dlllength:= 0;
            TmpBoolean := False;
            while Byte(ExporFunStr[i]) <> 0 do
            begin
              if (Byte(ExporFunStr[i]) <> 46) and (not TmpBoolean) then
              begin
                 ExporFunDllName[I] := ExporFunStr[i];
              end
              else
              if (Byte(ExporFunStr[i]) = 46) and (not TmpBoolean) then
              begin
                TmpBoolean := True;
                dlllength := i;
              end
              else
              begin
               ExporFunAPIName[I - 1 - dlllength] := ExporFunStr[i];
              end;
              I:=I+1;
              if i > 255 then
                Break;
            end;
            Byte(ExporFunAPIName[i]) := 0;
            //拼接字符串
            if dlllength > 1 then
            begin
              Byte(ExporFunDllName[dlllength]) := 46;
              Byte(ExporFunDllName[dlllength + 1]) := 68;
              Byte(ExporFunDllName[dlllength + 2]) := 76;
              Byte(ExporFunDllName[dlllength + 3]) := 76;
              Byte(ExporFunDllName[dlllength + 4]) := 0;
              Result := GetProcAddress(LoadLibraryA(ExporFunDllName),ExporFunAPIName);
              if Result = nil then
              begin
                 OutputDebugString('发现异常的搜索api');
                 OutputDebugStringA(api);
                 Exit;
              end;
            end;
            // 不管了，瞎构造的谁也管不了
          end;
       end;
   end;
end;

procedure CallDllMain(Const MemoryLibrary:THandle;dwReason:DWORD);
var
  Memory:Pointer;
  NTHeader:PImageNtHeaders;
  DllMain: function (dwHandle:THandle;dwReason:DWORD;dwReserved: Pointer): Boolean; stdcall;
begin
   Memory := Pointer(MemoryLibrary);
   NTHeader := PImageNtHeaders(Pointer(PImageDosHeader(Memory)._lfanew + NativeUInt(Memory)));
   if (NTHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress <> 0) and
      (NTHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size <> 0) then
   begin
    if NTHeader.OptionalHeader.AddressOfEntryPoint <> 0 then
      @DllMain := Pointer(NTHeader.OptionalHeader.AddressOfEntryPoint + NativeUInt(MemoryLibrary));
   end;

   DllMain(MemoryLibrary, dwReason,nil);
end;

end.
